package com.ruoyi.framework.web.service;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import com.ruoyi.common.constant.CacheConstants;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.constant.UserConstants;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.common.core.domain.entity.Usertree;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.core.redis.RedisCache;
import com.ruoyi.common.exception.BusiException;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.exception.user.*;
import com.ruoyi.common.utils.*;
import com.ruoyi.common.utils.ip.IpUtils;
import com.ruoyi.framework.manager.AsyncManager;
import com.ruoyi.framework.manager.factory.AsyncFactory;
import com.ruoyi.framework.security.context.AuthenticationContextHolder;
import com.ruoyi.system.dto.AddMemberDto;
import com.ruoyi.system.dto.RegisterDto;
import com.ruoyi.system.service.CashService;
import com.ruoyi.system.service.ISysConfigService;
import com.ruoyi.system.service.ISysUserService;
import com.ruoyi.system.service.UserTreeService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;

/**
 * 登录校验方法
 *
 * @author ruoyi
 */
@Component
public class SysLoginService {
    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;

    @Autowired
    private RedisCache redisCache;

    @Autowired
    private ISysUserService userService;

    @Autowired
    private ISysConfigService configService;
    @Resource
    private UserTreeService userTreeService;

    @Transactional
    public String phoneRegister(RegisterDto registerDto) {
//        String code = redisCache.getCacheObject("phone:" + registerDto.getPhone());
//        if (!StrUtil.equals(registerDto.getCode(), code)) {
//            throw new BusiException("验证码有误或已失效");
//        }
        SysUser sysUser = this.excute(registerDto.getInviteCode(), registerDto.getPhone(), registerDto.getPassword());
        LoginUser loginUser = BeanUtil.copyProperties(sysUser, LoginUser.class);
        loginUser.setUser(sysUser);
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        redisCache.deleteObject("phone:" + registerDto.getPhone());
        return tokenService.createToken(loginUser);
    }

    @Transactional
    public SysUser addMember(SysUser sysUser, AddMemberDto addMemberDto) {
        SysUser user = this.userService.getByUserName(addMemberDto.getUserName());
        if (user != null) {
            throw new BusiException("该账号已被占用");
        }
        user = new SysUser();
        BeanUtil.copyProperties(addMemberDto, user);
        //插入新用户信息
        user.setInviteCode(this.createInviteCode());
        if (StrUtil.isNotBlank(addMemberDto.getPassword())) {
            user.setPassword(SecurityUtils.encryptPassword(addMemberDto.getPassword()));
        }
        user.setRole("user");
        this.userService.addDomain(user);
        //创建关系树
        this.createUserTree(user, sysUser);
        cashService.getByUserId(user.getUserId());

        return user;
    }

    public String codeLogin(String username, String password, String code, String uuid) {
        // 验证码校验
        //validateCaptcha(username, code, uuid);
        String serCode = redisCache.getCacheObject("phone:" + username);
        if (!StrUtil.equals(serCode, code)) {
            throw new BusiException("验证码有误或已失效");
        }
        SysUser sysUser = userService.selectUserByUserName(username);
        if (sysUser == null) {
            throw new BusiException("该手机号未注册");
        }
        LoginUser loginUser = BeanUtil.copyProperties(sysUser, LoginUser.class);
        loginUser.setUser(sysUser);
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        redisCache.deleteObject("phone:" + username);
        return tokenService.createToken(loginUser);
    }

    /**
     * 登录验证
     *
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        // 验证码校验
        //  validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        try {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        } finally {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        //验证谷歌
        if (StrUtil.isNotBlank(loginUser.getRandomSecretKey())) {//如果用户绑定过谷歌

            if (StrUtil.isBlank(code)) {
                throw new BusiException("请输入谷歌验证码");
            }
            String rightCode = GoogleAuthenticationTool.getTOTPCode(loginUser.getRandomSecretKey());
            if (!StrUtil.equals(rightCode, code)) {
                throw new BusiException("谷歌验证码有误");
            }
        }
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        return tokenService.createToken(loginUser);
    }
    public LoginUser login1(String username, String password, String code, String uuid) {
        // 验证码校验
        //  validateCaptcha(username, code, uuid);
        // 登录前置校验
        loginPreCheck(username, password);
        // 用户验证
        Authentication authentication = null;
        try {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
                throw new UserPasswordNotMatchException();
            } else {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, e.getMessage()));
                throw new ServiceException(e.getMessage());
            }
        } finally {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        //验证谷歌
        if (StrUtil.isNotBlank(loginUser.getRandomSecretKey())) {//如果用户绑定过谷歌

            if (StrUtil.isBlank(code)) {
                throw new BusiException("请输入谷歌验证码");
            }
            String rightCode = GoogleAuthenticationTool.getTOTPCode(loginUser.getRandomSecretKey());
            if (!StrUtil.equals(rightCode, code)) {
                throw new BusiException("谷歌验证码有误");
            }
        }
        recordLoginInfo(loginUser.getUserId());
        // 生成token
        String token = tokenService.createToken(loginUser);


        loginUser.setToken(token);
        return  loginUser;
    }

    public String adminLogin(String username, String password, String code, String uuid) {
        // 验证码校验
        // validateCaptcha(username, code, uuid);
        return login(username, password, code, uuid);
    }

    /**
     * 校验验证码
     *
     * @param username 用户名
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public void validateCaptcha(String username, String code, String uuid) {
        boolean captchaEnabled = configService.selectCaptchaEnabled();
        if (captchaEnabled) {
            String verifyKey = CacheConstants.CAPTCHA_CODE_KEY + StringUtils.nvl(uuid, "");
            String captcha = redisCache.getCacheObject(verifyKey);
            redisCache.deleteObject(verifyKey);
            if (captcha == null) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.expire")));
                throw new CaptchaExpireException();
            }
            if (!code.equalsIgnoreCase(captcha)) {
                AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.jcaptcha.error")));
                throw new CaptchaException();
            }
        }
    }

    /**
     * 登录前置校验
     *
     * @param username 用户名
     * @param password 用户密码
     */
    public void loginPreCheck(String username, String password) {
        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            throw new UserNotExistsException();
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            throw new UserPasswordNotMatchException();
        }
        // IP黑名单校验
        String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BlackListException();
        }
    }

    /**
     * 记录登录信息
     *
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId) {
        SysUser sysUser = new SysUser();
        sysUser.setUserId(userId);
        sysUser.setLoginIp(IpUtils.getIpAddr());
        sysUser.setLoginDate(DateUtils.getNowDate());
        userService.updateUserProfile(sysUser);
    }

    @Resource
    private CashService cashService;

    private SysUser excute(String parentInviteCode, String phone, String password) {
        SysUser parent = this.checkParent(parentInviteCode);
        SysUser sysUser = new SysUser();
        sysUser.setUserName(phone);
        int count = userService.countByDomain(sysUser);
        if (count > 0) {
            throw new BusiException("该账号已被占用");
        }
        SysUser user = new SysUser();

        //插入新用户信息
        user.setInviteCode(this.createInviteCode());
        user.setUserName(phone);
        if (StrUtil.isNotBlank(password)) {
            user.setPassword(SecurityUtils.encryptPassword(password));
        }
        user.setBankRate(parent.getBankRate());
        user.setZfbRate(parent.getZfbRate());
        this.userService.addDomain(user);
        //创建关系树
        this.createUserTree(user, parent);

        cashService.getByUserId(user.getUserId());
        return user;
    }

    private SysUser checkParent(String parentInviteCode) {
        SysUser parent;
        if (org.springframework.util.StringUtils.isEmpty(parentInviteCode)) {//如果没有输入邀请码  默认总公司是他的邀请人
            parent = new SysUser();
            parent.setUserId(1L);
            return parent;
        }
        //如果输入了邀请码,则判断该邀请人是否存在
        SysUser user = new SysUser();
        user.setInviteCode(parentInviteCode);
        parent = this.userService.getDomain(user);
        if (parent == null) {
            throw new BusiException("邀请人不存在");

        }
        return parent;
    }

    private String createInviteCode() {
        String code;
        while (true) {
            code = MyStringUtil.createCode(6);
            SysUser sysUser = new SysUser();
            sysUser.setInviteCode(code);
            int i = userService.countByDomain(sysUser);
            if (i == 0) {
                break;
            }
        }
        return code;
    }

    private void createUserTree(SysUser user, SysUser parent) {
        //先构建一个自身的ut
        Usertree myself = new Usertree();
        myself.setParentId(user.getUserId());
        myself.setMemberId(user.getUserId());
        myself.setDist(0);
        //查询上级用户的关系树
        Usertree ut = new Usertree();
        ut.setMemberId(parent.getUserId());

        List<Usertree> newUtList = new ArrayList<>();
        newUtList.add(myself);
        List<Usertree> parentUtList = userTreeService.findList(ut);
        if (CollectionUtil.isEmpty(parentUtList)) {
            Usertree p = new Usertree();
            p.setParentId(parent.getUserId());
            p.setMemberId(parent.getUserId());
            p.setDist(0);
            newUtList.add(p);
        }
        for (int i = 0; i < parentUtList.size(); i++) {
            Usertree userTree = parentUtList.get(i);
            userTree.setId(null);
            userTree.setMemberId(user.getUserId());
            userTree.setDist(userTree.getDist() + 1);
            newUtList.add(userTree);
        }
        userTreeService.addDomainBatch(newUtList);
    }
}
